﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.ServiceProcess;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32;
using QW.Core.Domain;

namespace QW.Found.RunHelper
{
    /// <summary>
    /// 服务安装
    /// </summary>
    public class ServiceInstall
    {
        /// <summary>
        /// Windows 环境下运行
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="serviceName"></param>
        /// <param name="displayName"></param>
        /// <param name="serviceDescription"></param>
        /// <param name="args"></param>
        /// <param name="startRun"></param>
        /// <returns></returns>
        private static bool RunWin(string filePath, string serviceName, string displayName, string serviceDescription, string[] args, Action<string[]> startRun)
        {
            if (args.Length == 1)
            {
                Path.GetDirectoryName(filePath);
                if (File.Exists(Path.ChangeExtension(filePath, ".exe")))
                    filePath = Path.ChangeExtension(filePath, ".exe");
                else
                {
                    Environment.CurrentDirectory = Path.GetDirectoryName(typeof(ServiceInstall).Assembly.Location);
                    var text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "dotnet", "dotnet.exe");
                    if (File.Exists(text))
                        filePath = "\\\"" + text + "\\\" \\\"" + filePath + "\\\"";
                    else
                    {
                        text = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "dotnet", "dotnet.exe");
                        if (!File.Exists(text))
                        {
                            Console.WriteLine("系统无法定位DotNet Core的安装目录。");
                            return true;
                        }

                        filePath = "\\\"" + text + "\\\" \\\"" + filePath + "\\\"";
                    }
                }

                if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase))
                {
                    if (!AdminRestartApp(filePath, args))
                        return true;

                    Console.WriteLine(StartProcess("sc.exe", "create " + serviceName + " binPath= \"" + filePath + "\" start= auto"));
                    Console.WriteLine("Windows 下安装服务完成，如果失败请手动执行以下命令完成安装:");
                    Console.WriteLine("sc create " + serviceName + " binpath=\"" + filePath + "\" start=auto DisplayName=\"" + displayName + "\" //安装服务");
                    using (var registryKey = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\" + serviceName, writable: true))
                    {
                        registryKey.SetValue("DisplayName", displayName);
                        registryKey.SetValue("Description", serviceDescription);
                    }

                    return true;
                }

                if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase))
                {
                    if (!AdminRestartApp(filePath, args))
                        return true;

                    Console.WriteLine(StartProcess("sc.exe", "delete " + serviceName));
                    Console.WriteLine("Windows 下卸载服务完成，如果失败请手动执行以下命令完成卸载:");
                    Console.WriteLine("sc delete " + serviceName + "  //卸载服务");
                    return true;
                }
            }

            WinService.Config(startRun, serviceName);
            using (var service = new WinService())
            {
                ServiceBase.Run(service);
            }
            return false;
        }

        /// <summary>
        /// Unix环境下运行
        /// </summary>
        /// <param name="filePath"></param>
        /// <param name="serviceName"></param>
        /// <param name="serviceDescription"></param>
        /// <param name="args"></param>
        /// <param name="startRun"></param>
        /// <returns></returns>
        private static bool RunUnix(string filePath, string serviceName, string serviceDescription, string[] args, Action<string[]> startRun)
        {
            var text = "/usr/share/dotnet/dotnet " + Path.GetFileName(filePath);
            if (File.Exists("/usr/bin/dotnet"))
                text = "/usr/bin/dotnet " + Path.GetFileName(filePath);

            if (!Process.GetCurrentProcess().MainModule!.FileName!.EndsWith("dotnet", StringComparison.OrdinalIgnoreCase))
            {
                filePath = Process.GetCurrentProcess().MainModule!.FileName;
                text = filePath;
            }

            var directoryName = Path.GetDirectoryName(filePath);
            if (args.Length == 1)
            {
                if (args[0].Equals("-i", StringComparison.OrdinalIgnoreCase))
                {
                    var path = "/etc/systemd/system/" + serviceName + ".service";
                    File.WriteAllText(path, "[Unit]" + Environment.NewLine);
                    File.AppendAllText(path, "Description=" + serviceDescription + Environment.NewLine);
                    File.AppendAllText(path, "[Service]" + Environment.NewLine);
                    File.AppendAllText(path, "Type=simple" + Environment.NewLine);
                    File.AppendAllText(path, "GuessMainPID=true" + Environment.NewLine);
                    File.AppendAllText(path, "WorkingDirectory=" + directoryName + Environment.NewLine);
                    File.AppendAllText(path, "StandardOutput=journal" + Environment.NewLine);
                    File.AppendAllText(path, "StandardError=journal" + Environment.NewLine);
                    File.AppendAllText(path, "ExecStart=" + text + Environment.NewLine);
                    File.AppendAllText(path, "Restart=always" + Environment.NewLine);
                    File.AppendAllText(path, "[Install]" + Environment.NewLine);
                    File.AppendAllText(path, "WantedBy=multi-user.target" + Environment.NewLine);
                    Console.WriteLine(StartProcess("/usr/bin/systemctl", "enable " + serviceName + ".service"));
                    Console.WriteLine(StartProcess("/usr/bin/systemctl", "start " + serviceName + ".service"));
                    Console.WriteLine("Unix 下安装服务完成，如果失败请手动执行以下命令完成安装:");
                    Console.WriteLine("systemctl enable " + serviceName + ".service  //使自启动生效");
                    Console.WriteLine("systemctl start " + serviceName + ".service  //立即启动项目服务");
                    Console.WriteLine("systemctl status " + serviceName + ".service -l //查看服务状态");
                    var path2 = Path.Combine(Path.GetDirectoryName(filePath), "服务管理.txt");
                    File.AppendAllText(path2, "systemctl enable " + serviceName + ".service //使服务开机启动生效\r\n");
                    File.AppendAllText(path2, "systemctl disable " + serviceName + ".service //使服务开机启动失效\r\n");
                    File.AppendAllText(path2, "systemctl start " + serviceName + ".service //启动服务\r\n");
                    File.AppendAllText(path2, "systemctl stop " + serviceName + ".service //停止服务\r\n");
                    File.AppendAllText(path2, "systemctl status " + serviceName + ".service //查看服务状态\r\n");
                    return true;
                }

                if (args[0].Equals("-u", StringComparison.OrdinalIgnoreCase))
                {
                    var text2 = "/etc/systemd/system/" + serviceName + ".service";
                    Console.WriteLine(StartProcess("/usr/bin/systemctl", "disable " + serviceName + ".service"));
                    if (File.Exists(text2))
                        File.Delete(text2);

                    Console.WriteLine("Unix 下卸载服务完成，如果失败请手动执行以下命令完成卸载");
                    Console.WriteLine("systemctl disable " + serviceName + ".service  //使自启动失效");
                    Console.WriteLine("rm -y " + text2 + "  //删除服务文件");
                    return true;
                }
            }

            startRun(args);
            Thread.Sleep(-1);
            return false;
        }

        /// <summary>
        /// 运行程序，如果有命令-i或者-u则执行安装或卸载，否则执行startRun
        /// 请在Main函数中调用，服务显示名称请在Main函数增加[DisplayName()]特性，服务说明[Description]特性。
        /// Windiows下需要依赖System.ServiceProcess.ServiceController
        /// -i 表示安装服务
        /// -u 表示卸载服务
        /// </summary>
        /// <param name="serviceName">服务名称</param>
        /// <param name="args">启动程序的参数</param>
        /// <param name="startRun">实际程序运行的函数</param>
        /// <param name="displayName">服务显示名称</param>
        /// <param name="serviceDescription">服务说明</param>
        public static void Run(string serviceName, string[] args, Action<string[]> startRun, string displayName = null, string serviceDescription = null)
        {
            var text = string.Empty;
            if (args.Length == 1)
            {
                if (args[0] == "-d")
                {
                    startRun(args);
                    return;
                }

                var stackFrame = new StackFrame(1);
                if (string.IsNullOrWhiteSpace(serviceName))
                    serviceName = stackFrame.GetMethod()!.DeclaringType!.Assembly.GetName().Name;

                if (string.IsNullOrEmpty(displayName))
                {
                    var customAttributes = stackFrame.GetMethod()!.GetCustomAttributes(typeof(DisplayNameAttribute), inherit: true);
                    if (customAttributes.Length != 0)
                        displayName = (customAttributes[0] as DisplayNameAttribute).DisplayName;
                }

                if (string.IsNullOrEmpty(serviceDescription))
                {
                    var customAttributes2 = stackFrame.GetMethod()!.GetCustomAttributes(typeof(DescriptionAttribute), inherit: true);
                    if (customAttributes2.Length != 0)
                        serviceDescription = (customAttributes2[0] as DescriptionAttribute).Description;
                }

                text = stackFrame.GetMethod()!.DeclaringType!.Assembly.Location;
            }

            if (Environment.OSVersion.Platform == PlatformID.Win32NT)
            {
                if (!Process.GetCurrentProcess().MainModule!.ModuleName!.Equals("dotnet.exe", StringComparison.OrdinalIgnoreCase))
                {
                    text = Process.GetCurrentProcess().MainModule!.FileName;
                    Environment.CurrentDirectory = Path.GetDirectoryName(text);
                }

                RunWin(text, serviceName, displayName ?? serviceName, serviceDescription ?? serviceName, args, startRun);
            }
            else
            {
                RunUnix(text, serviceName ?? serviceName, serviceDescription ?? serviceName, args, startRun);
            }
        }

        private static string StartProcess(string fileName, string arguments)
        {
            var empty = string.Empty;
            using var process = new Process();
            process.StartInfo = new ProcessStartInfo
            {
                UseShellExecute = false,
                Arguments = arguments,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                CreateNoWindow = true,
                WorkingDirectory = Environment.CurrentDirectory,
                FileName = fileName
            };
            process.Start();
            process.WaitForExit();
            empty = process.StandardOutput.ReadToEnd();
            process.Close();
            return empty;
        }

        private static bool AdminRestartApp(string filePath, string[] args)
        {
            if (!IsAdministrator())
            {
                Console.WriteLine("重新以管理员启动" + filePath);
                var startInfo = new ProcessStartInfo
                {
                    UseShellExecute = true,
                    Arguments = string.Join(" ", args),
                    WorkingDirectory = Environment.CurrentDirectory,
                    FileName = filePath,
                    Verb = "runas"
                };
                try
                {
                    Process.Start(startInfo);
                }
                catch (Exception arg)
                {
                    Console.WriteLine($"重新以管理员启动失败：{arg}");
                }

                return false;
            }

            return true;
        }

        /// <summary>
        /// 判断是否是处于Administrator下允许m
        /// </summary>
        /// <returns></returns>
        private static bool IsAdministrator()
        {
            using var ntIdentity = WindowsIdentity.GetCurrent();
            return new WindowsPrincipal(ntIdentity).IsInRole(WindowsBuiltInRole.Administrator);
        }
    }
}
